﻿using System;
using System.Collections.Generic;
using System.Net;

namespace PacketRush.Core
{
    /// <summary>
    /// Connection implementation used by <see cref="PacketRush.Core.ReliableUdpClientNetworkingProvider"/> and 
    /// <see cref="PacketRush.Core.ReliableUdpServerNetworkingProvider"/>.
    /// </summary>
    public class ReliableUdpConnection : INetConnection
    {
        /// <summary>
        /// Used to assign new connection ids by incrementing the value
        /// for each new connection
        /// </summary>
        private static int _IdCounter = 1;

        private Dictionary<uint, AckWaitingPacket> _AckWaitingList = new Dictionary<uint, AckWaitingPacket>(16);

        /// <summary>
        /// Contains packets waiting for acknowledgements.
        /// </summary>
        internal Dictionary<uint, AckWaitingPacket> AckWaitingList
        {
            get { return _AckWaitingList; }
            set { _AckWaitingList = value; }
        }

        /// <summary>
        /// The number of packets that are waiting to be acknowledged.
        /// </summary>
        public int NumPacketsWaitingAcknowledgement
        {
            get
            {
                return _AckWaitingList.Count;
            }
        }

        private uint _NextExpectedPacketSequenceIndex = 1;

        /// <summary>
        /// Keeps track of the next expected seq index.
        /// </summary>
        public uint NextExpectedPacketSequenceIndex
        {
            get
            {
                return _NextExpectedPacketSequenceIndex;
            }
            internal set
            {
                _NextExpectedPacketSequenceIndex = value;
            }
        }

        /// <summary>
        /// Keeps tracks of packet seq indexes that are expected, expected packets are identified
        /// if a received packet has an id that is greater than the next expected packet seq index
        /// </summary>
        private Dictionary<uint, uint> _ExpectingPacketIdList = new Dictionary<uint, uint>(10);

        /// <summary>
        /// The number of packets we are expecting.
        /// </summary>
        public int NumExpectedPackets
        {
            get
            {
                return _ExpectingPacketIdList.Count;
            }
        }

        private int _Id;
        /// <summary>
        /// The id of this connection.
        /// </summary>
        public int Id
        {
            get
            {
                return _Id;
            }
            set
            {
                _Id = value;
            }
        }

        private EndPoint _RemoteEndPoint;

        /// <summary>
        /// The remote end point.
        /// </summary>
        public EndPoint RemoteEndPoint
        {
            get { return _RemoteEndPoint; }
        }

        private EndPoint _LocalEndPoint;
        /// <summary>
        /// The local end point of this connection.
        /// </summary>
        public EndPoint LocalEndPoint
        {
            get
            {
                return _LocalEndPoint;
            }
        }

        private bool _Connected;

        /// <summary>
        /// Determine if this connection is connected.
        /// </summary>
        public bool Connected
        {
            get
            {
                return _Connected;
            }
            internal set
            {
                _Connected = value;
            }
        }

        /// <summary>
        /// The latency in milliseconds of this connection.
        /// </summary>
        public int Latency { get; set; }

        /// <summary>
        /// Creates an instance.
        /// </summary>
        /// <param name="remoteEndPoint">The remote end point.</param>
        /// <param name="localEndPoint">The local end point.</param>
        public ReliableUdpConnection(EndPoint remoteEndPoint, EndPoint localEndPoint)
        {
            _RemoteEndPoint = remoteEndPoint;
            _LocalEndPoint = localEndPoint;
        }

        /// <summary>
        /// Determine wether we are expecting a packet with the given id.
        /// </summary>
        /// <param name="packetSequenceIndex">The packet id of a packet we are expecting.</param>
        /// <returns>True if we are expecting this packet.</returns>
        public bool ExpectingPacket(uint packetSequenceIndex)
        {
            return _ExpectingPacketIdList.ContainsKey(packetSequenceIndex);
        }

        /// <summary>
        /// Forget a packet seq index of a packet that we are expecting.
        /// </summary>
        /// <param name="packetSequenceIndex">The sequence index to forget.</param>
        public void ForgetExpecting(uint packetSequenceIndex)
        {
            _ExpectingPacketIdList.Remove(packetSequenceIndex);
        }

        /// <summary>
        /// Remember a packet seq index of a packet we are expecting.
        /// </summary>
        /// <param name="packetSequenceIndex">The sequence index to remember.</param>
        public void RememberExpecting(uint packetSequenceIndex)
        {
            _ExpectingPacketIdList.Add(packetSequenceIndex, packetSequenceIndex);
        }

        private uint _PacketSequenceIndexCounter = 1;

        /// <summary>
        /// Gets the next sequence index, must be used to assign an id to a packet that is
        /// just about to be sent.
        /// </summary>
        /// <returns>The next packet sequence index.</returns>
        public uint CreateNextPacketSequenceIndex()
        {
            return _PacketSequenceIndexCounter++;
        }

        /// <summary>
        /// Remove a packet that is waiting on an ack
        /// </summary>
        /// <param name="rawPacket">The packet</param>
        /// <returns>True if the ack waiting entry was removed,
        /// False if the ack waiting entry was not removed</returns>
        public bool RemoveFromAckWaiting(byte[] rawPacket)
        {
            PacketReader reader = new PacketReader((Packet)rawPacket);

            uint packetId = reader.ReadUInt32();

            if (_AckWaitingList.ContainsKey(packetId))
            {
                _AckWaitingList.Remove(packetId);
                return true;
            }

            return false;
        }

        /// <summary>
        /// Add a packet that is waiting for an ack
        /// </summary>
        /// <param name="rawPacket">The packet we are waiting on for an ack</param>
        /// <param name="timeSent">The time this packet was sent</param>
        public void AddToAckWaiting(byte[] rawPacket, long timeSent)
        {
            // Make sure we copy this data as this raw packet may be referenced elsewhere in the system
            // so we need a snapshot of how it is now
            byte[] packetCopy = new byte[rawPacket.Length];
            Buffer.BlockCopy(rawPacket, 0, packetCopy, 0, rawPacket.Length);

            // TODO: Should we test to see if this is not already added? It could be something severe if this happens since it should not :(
            _AckWaitingList.Add(Packet.ReadSequenceIndex(packetCopy), new AckWaitingPacket() { RawPacket = packetCopy, TimeSent = timeSent, Tries = 1 });
        }

        /// <summary>
        /// Assigns a new connection id to this connection
        /// </summary>
        public void AssignNewId()
        {
            Id = _IdCounter++;
        }

        /// <summary>
        /// Convert to string.
        /// </summary>
        /// <returns>A string.</returns>
        public override string ToString()
        {
            return String.Format("Id:{0}, LocalEndPoint:{1}, RemoteEndPoint:{2}", Id, _LocalEndPoint, RemoteEndPoint);
        }
    }


}